﻿using System;
using System.Net;
using System.Threading;
using Pfz.Threading;

namespace Pfz.Remoting
{
	/// <summary>
	/// This is a listener that uses MemoryMappedFiles for local communication and
	/// Tcp/Ip for remote communications.
	/// </summary>
	public sealed class HybridChannellerListener:
		ThreadSafeExceptionAwareDisposable,
		IChannellerListener
	{
		private ChannellerListener _tcp;

		private MmfChannellerListener _mmf;
		private int _bufferLength;
		private ManagedAutoResetEvent _event = new ManagedAutoResetEvent();
		private ManagedManualResetEvent _event2 = new ManagedManualResetEvent();

		/// <summary>
		/// Creates a new HibrydChanneller on the given port and using the given bufferLengths.
		/// </summary>
		public HybridChannellerListener(int port, int bufferLength)
		{
			_bufferLength = bufferLength;

			_tcp = new ChannellerListener(IPAddress.Any, port, bufferLength);
			_mmf = new MmfChannellerListener("Pfz.Remoting.HybridChanneller:" + port);

			UnlimitedThreadPool.Run(_RunMmf);
			UnlimitedThreadPool.Run(_RunTcp);
		}

		/// <summary>
		/// Releases the Tcp/Ip and MemoryMappedFile listeners.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				Disposer.Dispose(ref _tcp);
				Disposer.Dispose(ref _mmf);

				_result = null;

				Disposer.Dispose(ref _event);
				Disposer.Dispose(ref _event2);
			}

			base.Dispose(disposing);
		}

		private IChanneller _result;
		private object _tryAcceptLock = new object();
		/// <summary>
		/// Accepts a new channel, be it local or remote.
		/// </summary>
		public IChanneller TryAccept()
		{
			CheckUndisposed();

			lock(_tryAcceptLock)
			{
				var localEvent = _event;
				if (localEvent == null)
					return null;

				localEvent.WaitOne();

				var result =_result;
				var event2 = _event2;
				if (event2 == null)
					return null;

				_event2.Set();
				return result;
			}
		}

		private object _resultLock = new object();
		private void _RunTcp()
		{
			try
			{
				var event2 = _event2;
				var localEvent = _event;

				if (event2 == null || localEvent == null)
					return;

				while (true)
				{
					var result = _tcp.TryAccept();

					if (result == null)
					{
						Dispose();
						return;
					}

					lock (_resultLock)
					{
						event2.Reset();
						_result = result;
						localEvent.Set();
						event2.WaitOne();
					}
				}
			}
			catch(Exception exception)
			{
				Dispose(exception);
			}
		}
		private void _RunMmf()
		{
			try
			{
				var event2 = _event2;
				var localEvent = _event;

				if (event2 == null || localEvent == null)
					return;

				while (true)
				{
					var result = _mmf.TryAccept();

					if (result == null)
					{
						Dispose();
						return;
					}

					result.ChannelBufferLength = _bufferLength;

					lock (_resultLock)
					{
						event2.Reset();
						_result = result;
						localEvent.Set();
						event2.WaitOne();
					}
				}
			}
			catch(Exception exception)
			{
				Dispose(exception);
			}
		}
	}
}
